/*
 * Copyright 2004-2011 H2 Group.
 * Copyright 2011 James Moger.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.iciql.bytecode;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import com.iciql.IciqlException;
import com.iciql.Token;

/**
 * This class converts a method to a SQL Token by interpreting (decompiling) the
 * bytecode of the class.
 */
public class ClassReader {

	private static final boolean DEBUG = false;

	private byte[] data;
	private int pos;
	private Constant[] constantPool;
	private int startByteCode;
	private String methodName;

	private String convertMethodName;
	private Token result;
	private Stack<Token> stack = new Stack<Token>();
	private ArrayList<Token> variables = new ArrayList<Token>();
	private boolean endOfMethod;
	private boolean condition;
	private int nextPc;
	private Map<String, Object> fieldMap = new HashMap<String, Object>();

	private static void debug(String s) {
		if (DEBUG) {
			System.out.println(s);
		}
	}

	public Token decompile(Object instance, Map<String, Object> fields, String method) {
		this.fieldMap = fields;
		this.convertMethodName = method;
		Class<?> clazz = instance.getClass();
		String className = clazz.getName();
		debug("class name " + className);
		ByteArrayOutputStream buff = new ByteArrayOutputStream();
		try {
			InputStream in = clazz.getClassLoader().getResource(className.replace('.', '/') + ".class")
					.openStream();
			while (true) {
				int x = in.read();
				if (x < 0) {
					break;
				}
				buff.write(x);
			}
		} catch (IOException e) {
			throw new IciqlException("Could not read class bytecode", e);
		}
		data = buff.toByteArray();
		int header = readInt();
		debug("header: " + Integer.toHexString(header));
		int minorVersion = readShort();
		int majorVersion = readShort();
		debug("version: " + majorVersion + "." + minorVersion);
		int constantPoolCount = readShort();
		constantPool = new Constant[constantPoolCount];
		for (int i = 1; i < constantPoolCount; i++) {
			int type = readByte();
			switch (type) {
			case 1:
				constantPool[i] = ConstantString.get(readString());
				break;
			case 3: {
				int x = readInt();
				constantPool[i] = ConstantNumber.get(x);
				break;
			}
			case 4: {
				int x = readInt();
				constantPool[i] = ConstantNumber.get("" + Float.intBitsToFloat(x), x, Constant.Type.FLOAT);
				break;
			}
			case 5: {
				long x = readLong();
				constantPool[i] = ConstantNumber.get(x);
				i++;
				break;
			}
			case 6: {
				long x = readLong();
				constantPool[i] = ConstantNumber
						.get("" + Double.longBitsToDouble(x), x, Constant.Type.DOUBLE);
				i++;
				break;
			}
			case 7: {
				int x = readShort();
				constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.CLASS_REF);
				break;
			}
			case 8: {
				int x = readShort();
				constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.STRING_REF);
				break;
			}
			case 9: {
				int x = readInt();
				constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.FIELD_REF);
				break;
			}
			case 10: {
				int x = readInt();
				constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.METHOD_REF);
				break;
			}
			case 11: {
				int x = readInt();
				constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.INTERFACE_METHOD_REF);
				break;
			}
			case 12: {
				int x = readInt();
				constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.NAME_AND_TYPE);
				break;
			}
			default:
				throw new IciqlException("Unsupported constant pool tag: " + type);
			}
		}
		int accessFlags = readShort();
		debug("access flags: " + accessFlags);
		int classRef = readShort();
		debug("class: " + constantPool[constantPool[classRef].intValue()]);
		int superClassRef = readShort();
		debug(" extends " + constantPool[constantPool[superClassRef].intValue()]);
		int interfaceCount = readShort();
		for (int i = 0; i < interfaceCount; i++) {
			int interfaceRef = readShort();
			debug(" implements " + constantPool[constantPool[interfaceRef].intValue()]);
		}
		int fieldCount = readShort();
		for (int i = 0; i < fieldCount; i++) {
			readField();
		}
		int methodCount = readShort();
		for (int i = 0; i < methodCount; i++) {
			readMethod();
		}
		readAttributes();
		return result;
	}

	private void readField() {
		int accessFlags = readShort();
		int nameIndex = readShort();
		int descIndex = readShort();
		debug("    " + constantPool[descIndex] + " " + constantPool[nameIndex] + " " + accessFlags);
		readAttributes();
	}

	private void readMethod() {
		int accessFlags = readShort();
		int nameIndex = readShort();
		int descIndex = readShort();
		String desc = constantPool[descIndex].toString();
		methodName = constantPool[nameIndex].toString();
		debug("    " + desc + " " + methodName + " " + accessFlags);
		readAttributes();
	}

	private void readAttributes() {
		int attributeCount = readShort();
		for (int i = 0; i < attributeCount; i++) {
			int attributeNameIndex = readShort();
			String attributeName = constantPool[attributeNameIndex].toString();
			debug("        attribute " + attributeName);
			int attributeLength = readInt();
			int end = pos + attributeLength;
			if ("Code".equals(attributeName)) {
				readCode();
			}
			pos = end;
		}
	}

	void decompile() {
		int maxStack = readShort();
		int maxLocals = readShort();
		debug("stack: " + maxStack + " locals: " + maxLocals);
		int codeLength = readInt();
		startByteCode = pos;
		int end = pos + codeLength;
		while (pos < end) {
			readByteCode();
		}
		debug("");
		pos = startByteCode + codeLength;
		int exceptionTableLength = readShort();
		pos += 2 * exceptionTableLength;
		readAttributes();
	}

	private void readCode() {
		variables.clear();
		stack.clear();
		int maxStack = readShort();
		int maxLocals = readShort();
		debug("stack: " + maxStack + " locals: " + maxLocals);
		int codeLength = readInt();
		startByteCode = pos;
		if (methodName.startsWith(convertMethodName)) {
			result = getResult();
		}
		pos = startByteCode + codeLength;
		int exceptionTableLength = readShort();
		pos += 2 * exceptionTableLength;
		readAttributes();
	}

	private Token getResult() {
		while (true) {
			readByteCode();
			if (endOfMethod) {
				return stack.pop();
			}
			if (condition) {
				Token c = stack.pop();
				Stack<Token> currentStack = new Stack<Token>();
				currentStack.addAll(stack);
				ArrayList<Token> currentVariables = new ArrayList<Token>();
				currentVariables.addAll(variables);
				int branch = nextPc;
				Token a = getResult();
				stack = currentStack;
				variables = currentVariables;
				pos = branch + startByteCode;
				Token b = getResult();
				if (a.equals("0") && b.equals("1")) {
					return c;
				} else if (a.equals("1") && b.equals("0")) {
					return Not.get(c);
				} else if (b.equals("0")) {
					return And.get(Not.get(c), a);
				} else if (a.equals("0")) {
					return And.get(c, b);
				} else if (b.equals("1")) {
					return Or.get(c, a);
				} else if (a.equals("1")) {
					return And.get(Not.get(c), b);
				}
				return CaseWhen.get(c, b, a);
			}
			if (nextPc != 0) {
				pos = nextPc + startByteCode;
			}
		}
	}

	private void readByteCode() {
		int startPos = pos - startByteCode;
		int opCode = readByte();
		String op;
		endOfMethod = false;
		condition = false;
		nextPc = 0;
		switch (opCode) {
		case 0:
			op = "nop";
			break;
		case 1:
			op = "aconst_null";
			stack.push(Null.INSTANCE);
			break;
		case 2:
			op = "iconst_m1";
			stack.push(ConstantNumber.get("-1"));
			break;
		case 3:
			op = "iconst_0";
			stack.push(ConstantNumber.get("0"));
			break;
		case 4:
			op = "iconst_1";
			stack.push(ConstantNumber.get("1"));
			break;
		case 5:
			op = "iconst_2";
			stack.push(ConstantNumber.get("2"));
			break;
		case 6:
			op = "iconst_3";
			stack.push(ConstantNumber.get("3"));
			break;
		case 7:
			op = "iconst_4";
			stack.push(ConstantNumber.get("4"));
			break;
		case 8:
			op = "iconst_5";
			stack.push(ConstantNumber.get("5"));
			break;
		case 9:
			op = "lconst_0";
			stack.push(ConstantNumber.get("0"));
			break;
		case 10:
			op = "lconst_1";
			stack.push(ConstantNumber.get("1"));
			break;
		case 11:
			op = "fconst_0";
			stack.push(ConstantNumber.get("0.0"));
			break;
		case 12:
			op = "fconst_1";
			stack.push(ConstantNumber.get("1.0"));
			break;
		case 13:
			op = "fconst_2";
			stack.push(ConstantNumber.get("2.0"));
			break;
		case 14:
			op = "dconst_0";
			stack.push(ConstantNumber.get("0.0"));
			break;
		case 15:
			op = "dconst_1";
			stack.push(ConstantNumber.get("1.0"));
			break;
		case 16: {
			int x = (byte) readByte();
			op = "bipush " + x;
			stack.push(ConstantNumber.get(x));
			break;
		}
		case 17: {
			int x = (short) readShort();
			op = "sipush " + x;
			stack.push(ConstantNumber.get(x));
			break;
		}
		case 18: {
			Token s = getConstant(readByte());
			op = "ldc " + s;
			stack.push(s);
			break;
		}
		case 19: {
			Token s = getConstant(readShort());
			op = "ldc_w " + s;
			stack.push(s);
			break;
		}
		case 20: {
			Token s = getConstant(readShort());
			op = "ldc2_w " + s;
			stack.push(s);
			break;
		}
		case 21: {
			int x = readByte();
			op = "iload " + x;
			stack.push(getVariable(x));
			break;
		}
		case 22: {
			int x = readByte();
			op = "lload " + x;
			stack.push(getVariable(x));
			break;
		}
		case 23: {
			int x = readByte();
			op = "fload " + x;
			stack.push(getVariable(x));
			break;
		}
		case 24: {
			int x = readByte();
			op = "dload " + x;
			stack.push(getVariable(x));
			break;
		}
		case 25: {
			int x = readByte();
			op = "aload " + x;
			stack.push(getVariable(x));
			break;
		}
		case 26:
			op = "iload_0";
			stack.push(getVariable(0));
			break;
		case 27:
			op = "iload_1";
			stack.push(getVariable(1));
			break;
		case 28:
			op = "iload_2";
			stack.push(getVariable(2));
			break;
		case 29:
			op = "iload_3";
			stack.push(getVariable(3));
			break;
		case 30:
			op = "lload_0";
			stack.push(getVariable(0));
			break;
		case 31:
			op = "lload_1";
			stack.push(getVariable(1));
			break;
		case 32:
			op = "lload_2";
			stack.push(getVariable(2));
			break;
		case 33:
			op = "lload_3";
			stack.push(getVariable(3));
			break;
		case 34:
			op = "fload_0";
			stack.push(getVariable(0));
			break;
		case 35:
			op = "fload_1";
			stack.push(getVariable(1));
			break;
		case 36:
			op = "fload_2";
			stack.push(getVariable(2));
			break;
		case 37:
			op = "fload_3";
			stack.push(getVariable(3));
			break;
		case 38:
			op = "dload_0";
			stack.push(getVariable(0));
			break;
		case 39:
			op = "dload_1";
			stack.push(getVariable(1));
			break;
		case 40:
			op = "dload_2";
			stack.push(getVariable(2));
			break;
		case 41:
			op = "dload_3";
			stack.push(getVariable(3));
			break;
		case 42:
			op = "aload_0";
			stack.push(getVariable(0));
			break;
		case 43:
			op = "aload_1";
			stack.push(getVariable(1));
			break;
		case 44:
			op = "aload_2";
			stack.push(getVariable(2));
			break;
		case 45:
			op = "aload_3";
			stack.push(getVariable(3));
			break;
		case 46: {
			Token index = stack.pop();
			Token ref = stack.pop();
			op = "iaload";
			stack.push(ArrayGet.get(ref, index));
			break;
		}
		case 47: {
			Token index = stack.pop();
			Token ref = stack.pop();
			op = "laload";
			stack.push(ArrayGet.get(ref, index));
			break;
		}
		case 48: {
			Token index = stack.pop();
			Token ref = stack.pop();
			op = "faload";
			stack.push(ArrayGet.get(ref, index));
			break;
		}
		case 49: {
			Token index = stack.pop();
			Token ref = stack.pop();
			op = "daload";
			stack.push(ArrayGet.get(ref, index));
			break;
		}
		case 50: {
			Token index = stack.pop();
			Token ref = stack.pop();
			op = "aaload";
			stack.push(ArrayGet.get(ref, index));
			break;
		}
		case 51: {
			Token index = stack.pop();
			Token ref = stack.pop();
			op = "baload";
			stack.push(ArrayGet.get(ref, index));
			break;
		}
		case 52: {
			Token index = stack.pop();
			Token ref = stack.pop();
			op = "caload";
			stack.push(ArrayGet.get(ref, index));
			break;
		}
		case 53: {
			Token index = stack.pop();
			Token ref = stack.pop();
			op = "saload";
			stack.push(ArrayGet.get(ref, index));
			break;
		}
		case 54: {
			int var = readByte();
			op = "istore " + var;
			setVariable(var, stack.pop());
			break;
		}
		case 55: {
			int var = readByte();
			op = "lstore " + var;
			setVariable(var, stack.pop());
			break;
		}
		case 56: {
			int var = readByte();
			op = "fstore " + var;
			setVariable(var, stack.pop());
			break;
		}
		case 57: {
			int var = readByte();
			op = "dstore " + var;
			setVariable(var, stack.pop());
			break;
		}
		case 58: {
			int var = readByte();
			op = "astore " + var;
			setVariable(var, stack.pop());
			break;
		}
		case 59:
			op = "istore_0";
			setVariable(0, stack.pop());
			break;
		case 60:
			op = "istore_1";
			setVariable(1, stack.pop());
			break;
		case 61:
			op = "istore_2";
			setVariable(2, stack.pop());
			break;
		case 62:
			op = "istore_3";
			setVariable(3, stack.pop());
			break;
		case 63:
			op = "lstore_0";
			setVariable(0, stack.pop());
			break;
		case 64:
			op = "lstore_1";
			setVariable(1, stack.pop());
			break;
		case 65:
			op = "lstore_2";
			setVariable(2, stack.pop());
			break;
		case 66:
			op = "lstore_3";
			setVariable(3, stack.pop());
			break;
		case 67:
			op = "fstore_0";
			setVariable(0, stack.pop());
			break;
		case 68:
			op = "fstore_1";
			setVariable(1, stack.pop());
			break;
		case 69:
			op = "fstore_2";
			setVariable(2, stack.pop());
			break;
		case 70:
			op = "fstore_3";
			setVariable(3, stack.pop());
			break;
		case 71:
			op = "dstore_0";
			setVariable(0, stack.pop());
			break;
		case 72:
			op = "dstore_1";
			setVariable(1, stack.pop());
			break;
		case 73:
			op = "dstore_2";
			setVariable(2, stack.pop());
			break;
		case 74:
			op = "dstore_3";
			setVariable(3, stack.pop());
			break;
		case 75:
			op = "astore_0";
			setVariable(0, stack.pop());
			break;
		case 76:
			op = "astore_1";
			setVariable(1, stack.pop());
			break;
		case 77:
			op = "astore_2";
			setVariable(2, stack.pop());
			break;
		case 78:
			op = "astore_3";
			setVariable(3, stack.pop());
			break;
		case 79: {
			// String value = stack.pop();
			// String index = stack.pop();
			// String ref = stack.pop();
			op = "iastore";
			// TODO side effect - not supported
			break;
		}
		case 80:
			op = "lastore";
			// TODO side effect - not supported
			break;
		case 81:
			op = "fastore";
			// TODO side effect - not supported
			break;
		case 82:
			op = "dastore";
			// TODO side effect - not supported
			break;
		case 83:
			op = "aastore";
			// TODO side effect - not supported
			break;
		case 84:
			op = "bastore";
			// TODO side effect - not supported
			break;
		case 85:
			op = "castore";
			// TODO side effect - not supported
			break;
		case 86:
			op = "sastore";
			// TODO side effect - not supported
			break;
		case 87:
			op = "pop";
			stack.pop();
			break;
		case 88:
			op = "pop2";
			// TODO currently we don't know the stack types
			stack.pop();
			stack.pop();
			break;
		case 89: {
			op = "dup";
			Token x = stack.pop();
			stack.push(x);
			stack.push(x);
			break;
		}
		case 90: {
			op = "dup_x1";
			Token a = stack.pop();
			Token b = stack.pop();
			stack.push(a);
			stack.push(b);
			stack.push(a);
			break;
		}
		case 91: {
			// TODO currently we don't know the stack types
			op = "dup_x2";
			Token a = stack.pop();
			Token b = stack.pop();
			Token c = stack.pop();
			stack.push(a);
			stack.push(c);
			stack.push(b);
			stack.push(a);
			break;
		}
		case 92: {
			// TODO currently we don't know the stack types
			op = "dup2";
			Token a = stack.pop();
			Token b = stack.pop();
			stack.push(b);
			stack.push(a);
			stack.push(b);
			stack.push(a);
			break;
		}
		case 93: {
			// TODO currently we don't know the stack types
			op = "dup2_x1";
			Token a = stack.pop();
			Token b = stack.pop();
			Token c = stack.pop();
			stack.push(b);
			stack.push(a);
			stack.push(c);
			stack.push(b);
			stack.push(a);
			break;
		}
		case 94: {
			// TODO currently we don't know the stack types
			op = "dup2_x2";
			Token a = stack.pop();
			Token b = stack.pop();
			Token c = stack.pop();
			Token d = stack.pop();
			stack.push(b);
			stack.push(a);
			stack.push(d);
			stack.push(c);
			stack.push(b);
			stack.push(a);
			break;
		}
		case 95: {
			op = "swap";
			Token a = stack.pop();
			Token b = stack.pop();
			stack.push(a);
			stack.push(b);
			break;
		}
		case 96: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "iadd";
			stack.push(Operation.get(a, Operation.Type.ADD, b));
			break;
		}
		case 97: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "ladd";
			stack.push(Operation.get(a, Operation.Type.ADD, b));
			break;
		}
		case 98: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "fadd";
			stack.push(Operation.get(a, Operation.Type.ADD, b));
			break;
		}
		case 99: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "dadd";
			stack.push(Operation.get(a, Operation.Type.ADD, b));
			break;
		}
		case 100: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "isub";
			stack.push(Operation.get(a, Operation.Type.SUBTRACT, b));
			break;
		}
		case 101: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "lsub";
			stack.push(Operation.get(a, Operation.Type.SUBTRACT, b));
			break;
		}
		case 102: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "fsub";
			stack.push(Operation.get(a, Operation.Type.SUBTRACT, b));
			break;
		}
		case 103: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "dsub";
			stack.push(Operation.get(a, Operation.Type.SUBTRACT, b));
			break;
		}
		case 104: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "imul";
			stack.push(Operation.get(a, Operation.Type.MULTIPLY, b));
			break;
		}
		case 105: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "lmul";
			stack.push(Operation.get(a, Operation.Type.MULTIPLY, b));
			break;
		}
		case 106: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "fmul";
			stack.push(Operation.get(a, Operation.Type.MULTIPLY, b));
			break;
		}
		case 107: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "dmul";
			stack.push(Operation.get(a, Operation.Type.MULTIPLY, b));
			break;
		}
		case 108: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "idiv";
			stack.push(Operation.get(a, Operation.Type.DIVIDE, b));
			break;
		}
		case 109: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "ldiv";
			stack.push(Operation.get(a, Operation.Type.DIVIDE, b));
			break;
		}
		case 110: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "fdiv";
			stack.push(Operation.get(a, Operation.Type.DIVIDE, b));
			break;
		}
		case 111: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "ddiv";
			stack.push(Operation.get(a, Operation.Type.DIVIDE, b));
			break;
		}
		case 112: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "irem";
			stack.push(Operation.get(a, Operation.Type.MOD, b));
			break;
		}
		case 113: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "lrem";
			stack.push(Operation.get(a, Operation.Type.MOD, b));
			break;
		}
		case 114: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "frem";
			stack.push(Operation.get(a, Operation.Type.MOD, b));
			break;
		}
		case 115: {
			Token b = stack.pop();
			Token a = stack.pop();
			op = "drem";
			stack.push(Operation.get(a, Operation.Type.MOD, b));
			break;
		}
		// case 116:
		// op = "ineg";
		// break;
		// case 117:
		// op = "lneg";
		// break;
		// case 118:
		// op = "fneg";
		// break;
		// case 119:
		// op = "dneg";
		// break;
		// case 120:
		// op = "ishl";
		// break;
		// case 121:
		// op = "lshl";
		// break;
		// case 122:
		// op = "ishr";
		// break;
		// case 123:
		// op = "lshr";
		// break;
		// case 124:
		// op = "iushr";
		// break;
		// case 125:
		// op = "lushr";
		// break;
		// case 126:
		// op = "iand";
		// break;
		// case 127:
		// op = "land";
		// break;
		// case 128:
		// op = "ior";
		// break;
		// case 129:
		// op = "lor";
		// break;
		// case 130:
		// op = "ixor";
		// break;
		// case 131:
		// op = "lxor";
		// break;
		// case 132: {
		// int var = readByte();
		// int off = (byte) readByte();
		// op = "iinc " + var + " " + off;
		// break;
		// }
		// case 133:
		// op = "i2l";
		// break;
		// case 134:
		// op = "i2f";
		// break;
		// case 135:
		// op = "i2d";
		// break;
		// case 136:
		// op = "l2i";
		// break;
		// case 137:
		// op = "l2f";
		// break;
		// case 138:
		// op = "l2d";
		// break;
		// case 139:
		// op = "f2i";
		// break;
		// case 140:
		// op = "f2l";
		// break;
		// case 141:
		// op = "f2d";
		// break;
		// case 142:
		// op = "d2i";
		// break;
		// case 143:
		// op = "d2l";
		// break;
		// case 144:
		// op = "d2f";
		// break;
		// case 145:
		// op = "i2b";
		// break;
		// case 146:
		// op = "i2c";
		// break;
		// case 147:
		// op = "i2s";
		// break;
		case 148: {
			Token b = stack.pop(), a = stack.pop();
			stack.push(new Function("SIGN", Operation.get(a, Operation.Type.SUBTRACT, b)));
			op = "lcmp";
			break;
		}
		// case 149:
		// op = "fcmpl";
		// break;
		// case 150:
		// op = "fcmpg";
		// break;
		// case 151:
		// op = "dcmpl";
		// break;
		// case 152:
		// op = "dcmpg";
		// break;
		case 153:
			condition = true;
			nextPc = getAbsolutePos(pos, readShort());
			stack.push(Operation.get(stack.pop(), Operation.Type.EQUALS, ConstantNumber.get(0)));
			op = "ifeq " + nextPc;
			break;
		case 154:
			condition = true;
			nextPc = getAbsolutePos(pos, readShort());
			stack.push(Operation.get(stack.pop(), Operation.Type.NOT_EQUALS, ConstantNumber.get(0)));
			op = "ifne " + nextPc;
			break;
		case 155:
			condition = true;
			nextPc = getAbsolutePos(pos, readShort());
			stack.push(Operation.get(stack.pop(), Operation.Type.SMALLER, ConstantNumber.get(0)));
			op = "iflt " + nextPc;
			break;
		case 156:
			condition = true;
			nextPc = getAbsolutePos(pos, readShort());
			stack.push(Operation.get(stack.pop(), Operation.Type.BIGGER_EQUALS, ConstantNumber.get(0)));
			op = "ifge " + nextPc;
			break;
		case 157:
			condition = true;
			nextPc = getAbsolutePos(pos, readShort());
			stack.push(Operation.get(stack.pop(), Operation.Type.BIGGER, ConstantNumber.get(0)));
			op = "ifgt " + nextPc;
			break;
		case 158:
			condition = true;
			nextPc = getAbsolutePos(pos, readShort());
			stack.push(Operation.get(stack.pop(), Operation.Type.SMALLER_EQUALS, ConstantNumber.get(0)));
			op = "ifle " + nextPc;
			break;
		case 159: {
			condition = true;
			nextPc = getAbsolutePos(pos, readShort());
			Token b = stack.pop(), a = stack.pop();
			stack.push(Operation.get(a, Operation.Type.EQUALS, b));
			op = "if_icmpeq " + nextPc;
			break;
		}
		case 160: {
			condition = true;
			nextPc = getAbsolutePos(pos, readShort());
			Token b = stack.pop(), a = stack.pop();
			stack.push(Operation.get(a, Operation.Type.NOT_EQUALS, b));
			op = "if_icmpne " + nextPc;
			break;
		}
		case 161: {
			condition = true;
			nextPc = getAbsolutePos(pos, readShort());
			Token b = stack.pop(), a = stack.pop();
			stack.push(Operation.get(a, Operation.Type.SMALLER, b));
			op = "if_icmplt " + nextPc;
			break;
		}
		case 162: {
			condition = true;
			nextPc = getAbsolutePos(pos, readShort());
			Token b = stack.pop(), a = stack.pop();
			stack.push(Operation.get(a, Operation.Type.BIGGER_EQUALS, b));
			op = "if_icmpge " + nextPc;
			break;
		}
		case 163: {
			condition = true;
			nextPc = getAbsolutePos(pos, readShort());
			Token b = stack.pop(), a = stack.pop();
			stack.push(Operation.get(a, Operation.Type.BIGGER, b));
			op = "if_icmpgt " + nextPc;
			break;
		}
		case 164: {
			condition = true;
			nextPc = getAbsolutePos(pos, readShort());
			Token b = stack.pop(), a = stack.pop();
			stack.push(Operation.get(a, Operation.Type.SMALLER_EQUALS, b));
			op = "if_icmple " + nextPc;
			break;
		}
		case 165: {
			condition = true;
			nextPc = getAbsolutePos(pos, readShort());
			Token b = stack.pop(), a = stack.pop();
			stack.push(Operation.get(a, Operation.Type.EQUALS, b));
			op = "if_acmpeq " + nextPc;
			break;
		}
		case 166: {
			condition = true;
			nextPc = getAbsolutePos(pos, readShort());
			Token b = stack.pop(), a = stack.pop();
			stack.push(Operation.get(a, Operation.Type.NOT_EQUALS, b));
			op = "if_acmpne " + nextPc;
			break;
		}
		case 167:
			nextPc = getAbsolutePos(pos, readShort());
			op = "goto " + nextPc;
			break;
		// case 168:
		// // TODO not supported yet
		// op = "jsr " + getAbsolutePos(pos, readShort());
		// break;
		// case 169:
		// // TODO not supported yet
		// op = "ret " + readByte();
		// break;
		// case 170: {
		// int start = pos;
		// pos += 4 - ((pos - startByteCode) & 3);
		// int def = readInt();
		// int low = readInt(), high = readInt();
		// int n = high - low + 1;
		// op = "tableswitch default:" + getAbsolutePos(start, def);
		// StringBuilder buff = new StringBuilder();
		// for (int i = 0; i < n; i++) {
		// buff.append(' ').append(low++).
		// append(":").
		// append(getAbsolutePos(start, readInt()));
		// }
		// op += buff.toString();
		// // pos += n * 4;
		// break;
		// }
		// case 171: {
		// int start = pos;
		// pos += 4 - ((pos - startByteCode) & 3);
		// int def = readInt();
		// int n = readInt();
		// op = "lookupswitch default:" + getAbsolutePos(start, def);
		// StringBuilder buff = new StringBuilder();
		// for (int i = 0; i < n; i++) {
		// buff.append(' ').
		// append(readInt()).
		// append(":").
		// append(getAbsolutePos(start, readInt()));
		// }
		// op += buff.toString();
		// // pos += n * 8;
		// break;
		// }
		case 172:
			op = "ireturn";
			endOfMethod = true;
			break;
		case 173:
			op = "lreturn";
			endOfMethod = true;
			break;
		case 174:
			op = "freturn";
			endOfMethod = true;
			break;
		case 175:
			op = "dreturn";
			endOfMethod = true;
			break;
		case 176:
			op = "areturn";
			endOfMethod = true;
			break;
		case 177:
			op = "return";
			// no value returned
			stack.push(null);
			endOfMethod = true;
			break;
		// case 178:
		// op = "getstatic " + getField(readShort());
		// break;
		// case 179:
		// op = "putstatic " + getField(readShort());
		// break;
		case 180: {
			String field = getField(readShort());
			Token p = stack.pop();
			String s = p + "." + field.substring(field.lastIndexOf('.') + 1, field.indexOf(' '));
			if (s.startsWith("this.")) {
				s = s.substring(5);
			}
			stack.push(Variable.get(s, fieldMap.get(s)));
			op = "getfield " + field;
			break;
		}
		// case 181:
		// op = "putfield " + getField(readShort());
		// break;
		case 182: {
			String method = getMethod(readShort());
			op = "invokevirtual " + method;
			if (method.equals("java/lang/String.equals (Ljava/lang/Object;)Z")) {
				Token a = stack.pop();
				Token b = stack.pop();
				stack.push(Operation.get(a, Operation.Type.EQUALS, b));
			} else if (method.equals("java/lang/Integer.intValue ()I")) {
				// ignore
			} else if (method.equals("java/lang/Long.longValue ()J")) {
				// ignore
			}
			break;
		}
		case 183: {
			String method = getMethod(readShort());
			op = "invokespecial " + method;
			break;
		}
		case 184:
			op = "invokestatic " + getMethod(readShort());
			break;
		// case 185: {
		// int methodRef = readShort();
		// readByte();
		// readByte();
		// op = "invokeinterface " + getMethod(methodRef);
		// break;
		// }
		case 187: {
			String className = constantPool[constantPool[readShort()].intValue()].toString();
			op = "new " + className;
			break;
		}
		// case 188:
		// op = "newarray " + readByte();
		// break;
		// case 189:
		// op = "anewarray " + cpString[readShort()];
		// break;
		// case 190:
		// op = "arraylength";
		// break;
		// case 191:
		// op = "athrow";
		// break;
		// case 192:
		// op = "checkcast " + cpString[readShort()];
		// break;
		// case 193:
		// op = "instanceof " + cpString[readShort()];
		// break;
		// case 194:
		// op = "monitorenter";
		// break;
		// case 195:
		// op = "monitorexit";
		// break;
		// case 196: {
		// opCode = readByte();
		// switch (opCode) {
		// case 21:
		// op = "wide iload " + readShort();
		// break;
		// case 22:
		// op = "wide lload " + readShort();
		// break;
		// case 23:
		// op = "wide fload " + readShort();
		// break;
		// case 24:
		// op = "wide dload " + readShort();
		// break;
		// case 25:
		// op = "wide aload " + readShort();
		// break;
		// case 54:
		// op = "wide istore " + readShort();
		// break;
		// case 55:
		// op = "wide lstore " + readShort();
		// break;
		// case 56:
		// op = "wide fstore " + readShort();
		// break;
		// case 57:
		// op = "wide dstore " + readShort();
		// break;
		// case 58:
		// op = "wide astore " + readShort();
		// break;
		// case 132: {
		// int var = readShort();
		// int off = (short) readShort();
		// op = "wide iinc " + var + " " + off;
		// break;
		// }
		// case 169:
		// op = "wide ret " + readShort();
		// break;
		// default:
		// throw new IciqlException(
		// "Unsupported wide opCode " + opCode);
		// }
		// break;
		// }
		// case 197:
		// op = "multianewarray " + cpString[readShort()] + " " + readByte();
		// break;
		// case 198: {
		// condition = true;
		// nextPc = getAbsolutePos(pos, readShort());
		// Token a = stack.pop();
		// stack.push("(" + a + " IS NULL)");
		// op = "ifnull " + nextPc;
		// break;
		// }
		// case 199: {
		// condition = true;
		// nextPc = getAbsolutePos(pos, readShort());
		// Token a = stack.pop();
		// stack.push("(" + a + " IS NOT NULL)");
		// op = "ifnonnull " + nextPc;
		// break;
		// }
		case 200:
			op = "goto_w " + getAbsolutePos(pos, readInt());
			break;
		case 201:
			op = "jsr_w " + getAbsolutePos(pos, readInt());
			break;
		default:
			throw new IciqlException("Unsupported opCode " + opCode);
		}
		debug("    " + startPos + ": " + op);
	}

	private void setVariable(int x, Token value) {
		while (x >= variables.size()) {
			variables.add(Variable.get("p" + variables.size(), null));
		}
		variables.set(x, value);
	}

	private Token getVariable(int x) {
		if (x == 0) {
			return Variable.THIS;
		}
		while (x >= variables.size()) {
			variables.add(Variable.get("p" + variables.size(), null));
		}
		return variables.get(x);
	}

	private String getField(int fieldRef) {
		int field = constantPool[fieldRef].intValue();
		int classIndex = field >>> 16;
		int nameAndType = constantPool[field & 0xffff].intValue();
		String className = constantPool[constantPool[classIndex].intValue()] + "."
				+ constantPool[nameAndType >>> 16] + " " + constantPool[nameAndType & 0xffff];
		return className;
	}

	private String getMethod(int methodRef) {
		int method = constantPool[methodRef].intValue();
		int classIndex = method >>> 16;
		int nameAndType = constantPool[method & 0xffff].intValue();
		String className = constantPool[constantPool[classIndex].intValue()] + "."
				+ constantPool[nameAndType >>> 16] + " " + constantPool[nameAndType & 0xffff];
		return className;
	}

	private Constant getConstant(int constantRef) {
		Constant c = constantPool[constantRef];
		switch (c.getType()) {
		case INT:
		case FLOAT:
		case DOUBLE:
		case LONG:
			return c;
		case STRING_REF:
			return constantPool[c.intValue()];
		default:
			throw new IciqlException("Not a constant: " + constantRef);
		}
	}

	private String readString() {
		int size = readShort();
		byte[] buff = data;
		int p = pos, end = p + size;
		char[] chars = new char[size];
		int j = 0;
		for (; p < end; j++) {
			int x = buff[p++] & 0xff;
			if (x < 0x80) {
				chars[j] = (char) x;
			} else if (x >= 0xe0) {
				chars[j] = (char) (((x & 0xf) << 12) + ((buff[p++] & 0x3f) << 6) + (buff[p++] & 0x3f));
			} else {
				chars[j] = (char) (((x & 0x1f) << 6) + (buff[p++] & 0x3f));
			}
		}
		pos = p;
		return new String(chars, 0, j);
	}

	private int getAbsolutePos(int start, int offset) {
		return start - startByteCode - 1 + (short) offset;
	}

	private int readByte() {
		return data[pos++] & 0xff;
	}

	private int readShort() {
		byte[] buff = data;
		return ((buff[pos++] & 0xff) << 8) + (buff[pos++] & 0xff);
	}

	private int readInt() {
		byte[] buff = data;
		return (buff[pos++] << 24) + ((buff[pos++] & 0xff) << 16) + ((buff[pos++] & 0xff) << 8)
				+ (buff[pos++] & 0xff);
	}

	private long readLong() {
		return ((long) (readInt()) << 32) + (readInt() & 0xffffffffL);
	}

}
